#include "CubeProg.h"
#include "transform3d.h"
#include "camera.h"
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QOpenGLFunctions_3_3_Core>


CubeProg::CubeProg(QObject *parent)
    : MaterialProg(parent)
    , MY_GL_FUNC(nullptr)
    , vertices(nullptr)
    , verticesBufLen(0)
    , program(nullptr)
    , vao(nullptr)
    , modelLoc(-1)
    , viewLoc(-1)
    , projectionLoc(-1)
    , viewPosLoc(-1)
{
}

CubeProg::~CubeProg()
{
    teardownGL();
}

void CubeProg::initializeGL(QOpenGLContext *ctx, void* arg, size_t argLen)
{
    MaterialProg::initializeGL(ctx, arg, argLen);

    MY_GL_FUNC = ctx->versionFunctions<QOpenGLFunctions_3_3_Core>();
    vertices = static_cast<GLfloat*>(arg);
    verticesBufLen = argLen;

    // create VAO
    bool rc = false;
    vao = new QOpenGLVertexArrayObject(this);
    rc = vao->create();
    Q_ASSERT(rc);

    vao->bind();

    // create program
    program = new QOpenGLShaderProgram(this);

    rc = program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/materials.vert");
    Q_ASSERT(rc);

    rc = program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/materials.frag");
    Q_ASSERT(rc);

    rc = program->link();
    Q_ASSERT(rc);

    rc = program->bind();
    Q_ASSERT(rc);

    program->enableAttributeArray(0);
    program->enableAttributeArray(1);
    program->setAttributeBuffer(0, GL_FLOAT, 0, 3, 6 * sizeof(GLfloat));
    program->setAttributeBuffer(1, GL_FLOAT, 3 * sizeof(GLfloat), 3, 6 * sizeof(GLfloat));

    // Cache Uniform Locations
    modelLoc = program->uniformLocation("model");
    Q_ASSERT(modelLoc >= 0);

    viewLoc = program->uniformLocation("view");
    Q_ASSERT(viewLoc >= 0);

    projectionLoc = program->uniformLocation("projection");
    Q_ASSERT(projectionLoc >= 0);

    viewPosLoc = program->uniformLocation("viewPos");
    Q_ASSERT(viewPosLoc >= 0);

    // Release (unbind) all
    program->release();

    vao->release();
}

void CubeProg::paintGL(const Camera &camera, const QMatrix4x4& projection, const Light &light)
{
    bool rc = false;

    rc = program->bind();
    Q_ASSERT(rc);

    program->setUniformValue(modelLoc, toMatrix());
    program->setUniformValue(viewLoc, camera.viewMatrix());
    program->setUniformValue(projectionLoc, projection);
    program->setUniformValue(viewPosLoc, camera.position);
    program->setUniformValue("normal", toMatrix().normalMatrix());

    program->setUniformValue("material.ambient", material_.ambient);
    program->setUniformValue("material.diffuse", material_.diffuse);
    program->setUniformValue("material.specular", material_.specular);
    program->setUniformValue("material.shininess", material_.shininess);

    program->setUniformValue("light.position", light.position);
    program->setUniformValue("light.ambient", light.ambient);
    program->setUniformValue("light.diffuse", light.diffuse);
    program->setUniformValue("light.specular", light.specular);

    vao->bind();
    MY_GL_FUNC->glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(verticesBufLen / (6*sizeof(GLfloat))));
    vao->release();

    program->release();
}

void CubeProg::teardownGL()
{
    if (program) {
        delete program;
        program = nullptr;
    }

    if (vao) {
        delete vao;
        vao = nullptr;
    }

    modelLoc = viewLoc = projectionLoc = -1;
    verticesBufLen = 0;
    vertices = nullptr;
    MY_GL_FUNC = nullptr;

    MaterialProg::teardownGL();
}
